package com.panopset.app;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.apache.commons.lang.StringEscapeUtils;

import com.panopset.Alert;
import com.panopset.Util;
import com.panopset.UtilIO;
import com.panopset.compat.RezIO;
import com.panopset.compat.Strings;
import com.panopset.io.DirectoryProcessor;
import com.panopset.io.FileProcessor;
import com.panopset.sf.PanTextField;
import com.panopset.sf.ThreadButton;
import com.panopset.sf.TitledFileSelection;
import com.panopset.sf.Windoc;
import com.panopset.sf.apps.PanopsetApp;

/**
 *
 * @author Karl Dinwiddie
 */
public class Gr extends PanopsetApp {

    /**
     * @param args
     *            Not used.
     */
    public static void main(final String... args) {
        new Gr().show();
    }

    @Override
    public JPanel createPanel(Windoc w) {
        return new Doc(w).getPanel();
    }

    private class Doc {
        private final Windoc w;
        private JPanel p;

        Doc(final Windoc windoc) {
            w = windoc;
        }

        void go() throws IOException {
            if (Strings.isPopulated(getFromTF().getText())) {
                GlobalReplace gr = new GlobalReplace(getDir().getFile(),
                        getFromTF().getText(), getToTF().getText(),
                        getExtListTF().getText());
                gr.setDropBlanks(getDropBlanksCB().isSelected());
                gr.setUnescapeJava(getUnescapeJavaCB().isSelected());
                gr.exec();
            }
            if (Strings.isPopulated(getDltDirTF().getText())) {
                List<File> dirs = new ArrayList<File>();
                new DeleteDirectories(getDir().getFile(), getDltDirTF()
                        .getText(), dirs).exec();
                for (File dir : dirs) {
                    Files.walkFileTree(dir.toPath(),
                            new SimpleFileVisitor<Path>() {
                                @Override
                                public FileVisitResult visitFile(Path file,
                                        BasicFileAttributes attrs)
                                        throws IOException {

                                    System.out
                                            .println("Deleting file: " + file);
                                    Files.delete(file);
                                    return FileVisitResult.CONTINUE;
                                }

                                @Override
                                public FileVisitResult postVisitDirectory(
                                        Path dir, IOException exc)
                                        throws IOException {

                                    System.out.println("Deleting dir: " + dir);
                                    if (exc == null) {
                                        Files.delete(dir);
                                        return FileVisitResult.CONTINUE;
                                    } else {
                                        throw exc;
                                    }
                                }
                            });
                }
            }
        }

        public JPanel getPanel() {
            if (p == null) {
                p = w.cf.createPanel(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridx = 0;
                gbc.gridy = 0;
                gbc.fill = GridBagConstraints.HORIZONTAL;
                gbc.weightx = 1;
                p.add(getDltDirTF().getPanel(), gbc);
                gbc.gridy++;
                p.add(getExtListTF().getPanel(), gbc);
                gbc.gridy++;
                p.add(getFromTF().getPanel(), gbc);
                gbc.gridy++;
                p.add(getToTF().getPanel(), gbc);
                JPanel cp2 = w.cf.createPanel(new GridBagLayout());
                GridBagConstraints gbc2 = new GridBagConstraints();
                gbc2.gridx = 0;
                gbc2.gridy = 0;
                gbc2.weightx = 1.0;
                gbc2.fill = GridBagConstraints.HORIZONTAL;
                cp2.add(getDir().getPanel(), gbc2);
                gbc2.weightx = 0;
                gbc2.gridx++;
                cp2.add(getGoBtn().getGui(), gbc2);
                gbc.gridy++;
                p.add(cp2, gbc);
                gbc.gridy++;
                JPanel cp3 = w.cf.createPanel(new GridBagLayout());
                GridBagConstraints gbc3 = new GridBagConstraints();
                gbc3.gridx = 0;
                gbc3.gridy = 0;
                gbc3.weightx = 1.0;
                gbc3.fill = GridBagConstraints.HORIZONTAL;
                cp3.add(getDropBlanksCB(), gbc3);
                gbc3.gridx++;
                cp3.add(getUnescapeJavaCB(), gbc3);
                p.add(cp3, gbc);

            }
            return p;
        }

        private JCheckBox unescapeJavaCB;

        public JCheckBox getUnescapeJavaCB() {
            if (unescapeJavaCB == null) {
                unescapeJavaCB = new JCheckBox(
                        Util.x("Unescape Java in result."), false);
                unescapeJavaCB.setToolTipText(Util
                        .x("Unescape Java escape characters such as \\n.  "));
                w.cf.registerPassiveComponent(unescapeJavaCB);
            }
            return unescapeJavaCB;
        }

        private JCheckBox dropBlanksCB;

        public JCheckBox getDropBlanksCB() {
            if (dropBlanksCB == null) {
                dropBlanksCB = new JCheckBox(Util.x("Drop blanks"), false);
                dropBlanksCB
                        .setToolTipText(Util
                                .x("Ignore any lines that are not populated.  "

                                        + "Useful for changing double spaced documents to single spaced."));
                w.cf.registerPassiveComponent(dropBlanksCB);
            }
            return dropBlanksCB;
        }

        /**
         * Go button.
         */
        private ThreadButton goBtn;

        private ThreadButton getGoBtn() {
            if (goBtn == null) {
                goBtn = new ThreadButton() {

                    @Override
                    public void run() {
                        try {
                            go();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    @Override
                    public String createText() {
                        return ("Go");
                    }

                    @Override
                    public String createTipText() {
                        return Util.x("Not undoable, is everything backed up?");
                    }

                    @Override
                    public char createMnemonic() {
                        return KeyEvent.VK_G;
                    }

                    @Override
                    public String createThreadName() {
                        return "replacer";
                    }

                };
                w.cf.registerPassiveComponent(goBtn.getGui());
            }
            return goBtn;
        }

        /**
         * Text to replace.
         */
        private PanTextField fromTF;

        /**
         * @return fromTF.
         */
        private PanTextField getFromTF() {
            if (fromTF == null) {
                fromTF = w.cf.createTitledField(w, 80, Util.x("Replace"),
                        Util.x("Text you would like to replace everywhere."),
                        "replace");
            }
            return fromTF;
        }

        /**
         * Replacement text.
         */
        private PanTextField toTF;

        /**
         * @return fromTF.
         */
        private PanTextField getToTF() {
            if (toTF == null) {
                toTF = w.cf.createTitledField(w, 80, Util.x("with"),
                        Util.x("Replacement text."), "with");
            }
            return toTF;
        }

        /**
         * Replacement text.
         */
        private PanTextField extListTF;

        /**
         * @return fromTF.
         */
        PanTextField getExtListTF() {
            if (extListTF == null) {
                extListTF = w.cf.createTitledField(w, 80,
                        Util.x("File or extension list."),
                        Util.x("All files if left blank."

                        + " Comma separated, like: .txt,.java,something.xml"),
                        "extlist");
            }
            return extListTF;
        }

        /**
         * Delete directories.
         */
        private PanTextField dltDirTF;

        private PanTextField getDltDirTF() {
            if (dltDirTF == null) {
                dltDirTF = w.cf.createTitledField(w, 80,
                        Util.x("Delete directories with this name."),
                        Util.x("Ignored if blank."), "dltdirs");
            }
            return dltDirTF;
        }

        /**
         * Directory to recursively traverse.
         */
        private TitledFileSelection dir;

        /**
         * @return Directory to recursively traverse.
         */
        private TitledFileSelection getDir() {
            if (dir == null) {
                dir = new TitledFileSelection(w, Util.x("Directory"),
                        Util.x("Directory to recursively traverse."),
                        JFileChooser.DIRECTORIES_ONLY,
                        JFileChooser.OPEN_DIALOG, "dir");
            }
            return dir;
        }
    }

    public final class DeleteDirectories extends DirectoryProcessor {

        final String nm;
        final List<File> dirs;

        public DeleteDirectories(File dir, String name, List<File> directories) {
            super(dir);
            nm = name;
            dirs = directories;
        }

        @Override
        protected void process(File f) {

        }
        @Override
        protected void processDir(File dir) {
            if (dir.getName().equals(nm)) {
                dirs.add(dir);
            } else {
                dir.setWritable(true);
            }
        }

    }

    public final class GlobalReplace extends DirectoryProcessor {

        /**
         * Evaluate escapes flag.
         */
        private boolean unescapeJava;

        /**
         *
         * @param value
         *            Escape characters will be evaluated if this is set to
         *            true.
         */
        public void setUnescapeJava(final boolean value) {
            unescapeJava = value;
        }

        /**
         * Drop blanks flag.
         */
        private boolean dropBlanks;

        /**
         *
         * @param value
         *            Blank lines will be dropped during global replace if this
         *            is set to true.
         */
        public void setDropBlanks(final boolean value) {
            dropBlanks = value;
        }

        /**
         * From String.
         */
        private final String from;

        /**
         * To String.
         */
        private final String to;

        /**
         * Extension to limit file types to.
         */
        private final List<String> ext;

        /**
         * @param dir
         *            Directory to traverse.
         * @param fromText
         *            String to replace.
         * @param toText
         *            Replacement String.
         * @param extension
         *            File type to limit processing to, for example
         *            &quot;.txt&quot;. All files will be processed if extension
         *            is null or blank. Comma separated values accepted as well.
         */
        public GlobalReplace(final File dir, final String fromText,
                final String toText, final String extension) {
            super(dir);
            from = fromText;
            to = toText;
            ext = new ArrayList<String>();
            StringTokenizer st = new StringTokenizer(extension, ",");
            while (st.hasMoreElements()) {
                String s = st.nextToken().trim();
                ext.add(s);
            }
        }

        private void doReplacementsOn(File f) {
            if (ext.isEmpty() || ext.contains(UtilIO.getExtension(f))
                    || ext.contains(f.getName())) {
                Alert.green("Processing " + RezIO.getCanonicalPath(f));
                final StringWriter sw = new StringWriter();
                new FileProcessor.Builder()
                        .fileAndListener(f, new FileProcessor.Listener() {

                            @Override
                            public void process(final FileProcessor processor,
                                    final String[] values) {
                                for (String s : values) {
                                    if (dropBlanks && !Strings.isPopulated(s)) {
                                        continue;
                                    }
                                    String rslt = s.replace(from, to);
                                    if (unescapeJava) {
                                        rslt = StringEscapeUtils
                                                .unescapeJava(rslt);
                                    }
                                    sw.append(rslt);
                                    sw.append(Strings.getEol());
                                }
                            }

                            @Override
                            public void finish(final FileProcessor processor) {
                                // nothing to do here.
                            }

                        }).construct().exec();
                UtilIO.saveStringToFile(sw.toString(), f);
            }
        }

        @Override
        protected void process(final File f) {
            doReplacementsOn(f);
        }
    }
}
